// Copyright 2019 The rhyme_lph Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// A [author] rhyme_lph
// p [email]  rhymelph@gmail.com
// L [github] https://github.com/rhymelph

import 'package:base_plugin/base_plugin.dart';
import 'package:dio/src/response.dart';
import 'package:finance_app/generated/l10n.dart';
import 'package:finance_app/src/common/config.dart';
import 'package:finance_app/src/entities/dashboard_leave_entity.dart';
import 'package:finance_app/src/entities/employee_contact_entity.dart';
import 'package:finance_app/src/entities/leave_item_entity.dart';
import 'package:finance_app/src/entities/leave_item_value.dart';
import 'package:finance_app/src/http_service/http_leave_service.dart';
import 'package:finance_app/src/mixin/filter_leave_mixin.dart';
import 'package:finance_app/src/mixin/refresh_mixin.dart';
import 'package:finance_app/src/router_gen/router_gen_providers.dart';
import 'package:finance_app/src/utils/date_time_utils.dart';
import 'package:finance_app/src/utils/form_utils.dart';
import 'package:finance_app/src/utils/list_to_map_utils.dart';
import 'package:finance_app/src/widgets/bottom_sheet/filter_bottom_sheet.dart';
import 'package:flutter/material.dart';
import 'package:finance_app/src/page/hr_payroll/views/leave_view.dart';
import 'dart:async';

import '../hr_and_payroll_provider.dart';

class LeaveProvider extends BaseProvider<List<DashboardLeaveEntity>>
    with
        RefreshMixin<List<DashboardLeaveEntity>, List<LeaveItemEntity>>,
        FilterLeaveMixin<List<DashboardLeaveEntity>> {
  Map<String, LeaveItemValue> leaveMap = {
    'pending': LeaveItemValue(isOpen: true),
    'approved': LeaveItemValue(),
    'rejected': LeaveItemValue(),
    'withdrew': LeaveItemValue(),
  };
  String currentType = 'pending';

  String get filterName =>
      currentSelectFilterList.map((e) => e.toLowerCase()).join(',');
  EmployeeContactEntity currentSelectEmployee;

  void onChangeType(String type) {
    currentType = type;
    _loadLeaves(isShowLoading: true);
  }

  ScrollController controller = ScrollController();

  @override
  void initState() {
    super.initState();
    leaveMap.entries.forEach((element) {
      element.value.controller.addListener(_handleLeaveListener);
    });
    controller.addListener(() {
      getParentProvider<HrAndPayrollProvider>().handleScrollUpdate(controller);
    });
  }

  void _handleLeaveListener() {
    bool hasOpen = false;
    String key;
    for (final item in leaveMap.entries) {
      if (item.value.controller.value == true) {
        hasOpen = true;
        key = item.key;
        break;
      }
    }
    if (hasOpen == true) {
      onChangeType(key);
    } else {
      // leaveMap[currentType].allData = null;
      leaveMap[currentType].groupMap.clear();
      currentType = null;
      update();
    }
  }

  @override
  Future<Response> get dataFuture async {
    if (Config.isStaff) {
      currentSelectEmployee = await Config.getEmployeeMe(this, false);
    }
    return await HttpLeaveService.dashboardLeaves(currentSelectEmployee?.id);
  }

  void onRefreshDashboard() {
    post(dataFuture, autoHandler: AutoHandler.Toast, onSuccess: (result) {
      data = getEntityFromResult<List<DashboardLeaveEntity>>(result);
      update();
    });
  }

  void onChangeEmployee() async {
    final result = await Routes.navigateTo(
        RouterGenProviders.employeeContactsPage,
        params: {
          'select': currentSelectEmployee,
          'canAdd': false,
          'showAll': true,
        });
    if (result != null && result is EmployeeContactEntity) {
      currentSelectEmployee = result;
      if (result.id == -1) {
        currentSelectEmployee = null;
      }
      update();
      obtainContext?.call((context) async {
        BaseConfig.dialogBase
            .showLoadingDialog(context: context, text: S.current.loading);
        await _loadDashBoard();
        await _loadLeaves();
        Navigator.of(context).pop();
      });
    }
  }

  @override
  FutureOr<List<DashboardLeaveEntity>> transformData(
      List<DashboardLeaveEntity> d, bool isCache) async {
    await _loadLeaves();
    return super.transformData(d, isCache);
  }

  Future<void> _loadLeaves({bool isShowLoading = false}) async {
    String tempType = currentType;
    if (currentType == null) {
      currentType = "pending"; // to refresh pending numbers
    }

    leaveMap[currentType]?.groupMap?.clear();
    page = 1;
    await post(refreshFuture,
        autoHandler: AutoHandler.Toast,
        loadingTip: isShowLoading ? S.current.loading : null,
        onSuccess: (result) {
      leaveMap[currentType].allData =
          getEntityFromResult<List<LeaveItemEntity>>(result);
      hasUp = leaveMap[currentType].allData.length >= rows;
      contactToMap(leaveMap[currentType].allData);
      currentType = tempType;
      update();
    });
  }

  void contactToMap(List<LeaveItemEntity> payslipsList) {
    Map<String, List<LeaveItemEntity>> groupMap =
        leaveMap[currentType].groupMap;

    for (final item in payslipsList) {
      final month =
          ListToMapUtils.groupMonth<LeaveItemEntity>(item, (t) => t.startDate);
      if (groupMap.containsKey(month)) {
        groupMap[month].add(item);
      } else {
        groupMap[month] = [item];
      }
    }
  }

  @override
  List<LeaveItemEntity> get dataList =>
      currentType == null ? [] : leaveMap[currentType].allData;

  @override
  List<LeaveItemEntity> getFromRequestResult(
      Response<dynamic> result, bool isRefresh) {
    return getEntityFromResult<List<LeaveItemEntity>>(result);
  }

  @override
  Future get refreshFuture =>
      HttpLeaveService.leavesList(currentSelectEmployee?.id, currentType, page);

  void onNavDetail(LeaveItemEntity e) async {
    final result =
        await Routes.navigateTo(RouterGenProviders.newLeavePage, params: {
      'data': e,
    });
    if (result != null && result is bool) {
      leaveMap[currentType].allData.remove(e);
      leaveMap[currentType].groupMap.clear();
      contactToMap(leaveMap[currentType].allData);
      update();
    }
  }

  void updateStatus(int id, String s, [String content]) {
    final find = leaveMap[currentType]
        .allData
        .firstWhere((element) => element.id == id, orElse: () => null);
    find.status = s;
    find.rejectedReason = content;
    update();
  }

  void addItem(LeaveItemEntity result) {
    leaveMap[currentType].allData.add(result);
    update();
  }

  void deleteItem(int id) {
    leaveMap[currentType].allData.removeWhere((e) => e.id == id);
    update();
  }

  @override
  void onRequestRefresh() {
    obtainContext?.call((context) async {
      BaseConfig.dialogBase
          .showLoadingDialog(context: context, text: S.current.loading);
      await _loadDashBoard();
      await _loadLeaves();
      Navigator.of(context).pop();
    });
  }

  @override
  void unFocus() {}

  String getLeaveStatus(LeaveItemEntity e) {
    if (e.status == 'approved') {
      final dateTime = DateTime.now();
      final startTime = DateTimeUtils.toDateTime(e.startDate, 'yyyy-MM-dd');
      return startTime.isBefore(dateTime) ? null : 'Upcoming';
    }
    return FormUtils.getStatus(ReimburseModel.leave, e.status, 0);
  }

  Future<void> _loadDashBoard() async {
    await post(dataFuture, autoHandler: AutoHandler.Toast, onSuccess: (result) {
      data = getEntityFromResult<List<DashboardLeaveEntity>>(result);
      // if (!canRefreshFuture) {
      //   update();
      // }
    });
  }

  String getGroupName(String key) {
    if (key == 'pending') {
      return S.current.pending_leaves_;
    } else if (key == 'approved') {
      return S.current.approved_leaves;
    } else if (key == 'rejected') {
      return S.current.rejected_leaves;
    } else if (key == 'withdrew') {
      return S.current.withdraw_leaves;
    } else {
      return key;
    }
  }

  void refreshDashboard() {
    _loadDashBoard();
    _loadLeaves();
  }

  @override
  Future<void> onRefresh() async {
    await _loadDashBoard();
    await _loadLeaves();
    return super.onRefresh();
  }

  @override
  bool get canRefreshFuture => currentType != null;
}
